The ServiceContract attribute and the OperationContract
attribute can be directly applied to the class. In this scenario, the
WCF runtime infers the interface from the class. This practice is not
recommended, as it runs contrary to the Standardized Service Contract
Example 5.
<endpoint name="EndPoint1" address="net.tcp://localhost:1234" binding="netTcpBinding" contract="IGreetings" />
|
Service endpoints are discussed shortly. First, we need to cover the DataContract and MessageContract attributes.
Data Models and the DataContract Attribute
In WCF, the DataContract attribute is used to generate an XML schema from a CLR type. Members of a data type will not be serialized unless the DataMember attribute is applied, allowing members of the data type to opt-in. The DataContract construct uses XmlFormatter
to handle serialization and deserialization of the CLR type, thereby
abstracting the complexity involved with generating XML schemas. XmlFormatter is also used to de-serialize the XML schema into a CLR type allowing it to be easily consumed by service consumers.
As mentioned earlier, a primary focal point when applying the Standardized Service Contract
principle is the standardization of the data model exposed by the
contract. In fact, this fundamental practice is the basis of Canonical
Schema ,
the pattern that is applied specifically for establishing a consistent
and standardized data architecture. The XML Schema Definition Language
(XML Schema) has become the de-facto means of defining data models for
service contracts that can be shared across platforms. The Validation
Abstraction
pattern is further commonly applied to ensure that schema content is
balanced and optimized in support of the Service Abstraction principle and to avoid negative consumer-to-contract coupling, as per the Service Loose Coupling principle.
|
WCF comes with two XML serializers, namely XmlSerializer and XmlFormatter. XmlFormatter is the successor to XmlSerializer and is the default implementation in WCF. It is used when the DataContract and DataMember
attributes are used in a CLR data type. Data contracts are passed
between the service and its consumer. The class shown in the following
example is a CLR type used to return data from the service:
Example 6.
public class Account { public string Account_Number; public string Account_Name; public string Type; public string Cash; public string Investments; }
|
The CLR type can be serialized to an XML schema document by applying the DataContract and DataMember attributes, as shown here:
Example 7.
using System.Runtime.Serialization; [DataContract(Namespace = "Finance", Name = "AccountContract")] public class Account { [DataMember(Name = "Account_Number")] public string Account_Number; [DataMember(Name = "Account_Name")] public string Account_Name; [DataMember(Name = "Type", Order = 1)] public string Type; [DataMember(Name = "Cash", Order = 1)] public string Cash; [DataMember(Name = "Investments", Order = 2)] public string Investments; [DataMember(Name = "Total", Order = 2)] public string Total; }
|
The DataContract
attribute forces the CLR data type to be serializable in order to be
used as a parameter in an operation. One data contract can be a
sub-class of another data contract.
XmlFormatter provides less control over how data is serialized to XML. By limiting flexibility, data contracts created using XmlFormatter are optimized for performance and support better versioning. On the other hand, XmlSerializer provides very precise control over how data is represented in XML.
Messaging and the MessageContract Attribute
At its core, WCF uses messages
for communication and has a deep commitment to implementing these
messages using industry standard protocols, namely SOAP and XML Schema (Figure 1).
Messages can be
transmitted using industry standard transport protocols, such as HTTP,
HTTPS, and TCP, as well as MSMQ and named pipes. Support for sending messages
over custom transport protocols is also provided. Both simple and
complex message exchange patterns (MEPs) can be expressed, including
synchronous and asynchronous interchanges.
Higher level WCF features
that layer on top of the messaging framework include extensions for
reliability, transactions, queuing, and security features, such as
encryption for message confidentiality and digital signing for message
integrity.
The message class in the System.ServiceModel
namespace can be used to access the contents of a message at the most
basic level. By default, these messages are untyped; they conform to
the SOAP Envelope construct and can therefore further contain both Body and Header constructs.
Two fundamental patterns that can be realized with the use of SOAP messages are Service Messaging and Messaging Metadata,
the latter of which directly relates to the usage and function of SOAP
message headers. The message content structure itself is generally
defined using XML Schema, which is often regulated by the application
of Canonical Schema together with the Standardized Service Contract , Service Loose Coupling , and Service Abstraction principles.
|
Message contracts are a
WCF mechanism used to exercise precise control over the processing of
SOAP messages. Whenever a program needs to receive and process message
content, it needs to undergo a complete data transformation in order
for the underlying service logic to accept it and use it.
The WCF API includes message-related classes that allow you to work at a very granular level. These classes include MessageContract, MessageHeader and MessageBody, all of which are used as attributes to describe the structure of SOAP messages.
In the following code listing, the XML schema produced by the MsgHeader data contract will appear in the SOAP header block. The MsgBody data contract will appear in the SOAP body.
Example 8.
[DataContract] public class MsgHeader { [DataMember] public string loginName; [DataMember] public string password; } [DataContract] public class MsgBody { [DataMember(Order=1)] public string name; [DataMember(Order=2)] public int phone; [DataMember(Order=3)] public int email; } [MessageContract] public class ContactMessage { [MessageHeader] public MsgHeader msgHeader; [MessageBody] public MsgBody msgBody; }
|
To use the MessageContract developed in Example 5.8, the parameter of the operation needs to be changed, as shown here:
Example 9.
[ServiceContract] public interface IContactService { [OperationContract] public void InsertContact(ContactMessage contactMessage); }
|
Prior to using the message contract, the SOAP message on the wire would exist as follows:
Example 10.
<S:Envelope> <S:Header> </S:Header> <S:Body> </S:Body> </S:Envelope>
|
The InsertContact operation would now create the following SOAP message:
Example 11.
<S:Envelope> <S:Header> <S:loginName>user</S:loginName> <S:password>password</S:password> </S:Header> <S:Body> <S:name> ... </S:name> <S:phone> ... </S:phone> <S:email> ... </S:email> </S:Body> </S:Envelope>
|
Note
.NET version 3.5 SP1 expanded the reach of the data contract serializer by relaxing the need of having [DataContract]/[DataMember] on types and by supporting an interoperable mechanism for dealing with object references.
Service Endpoints and the endpoint Element
The information required for a service consumer to invoke a given service is expressed with the endpoint element, which effectively establishes what is called a service endpoint.
Specifically, a service endpoint contains the following parts:
address – the location of the service binding – service invocation requirements (including information about security and reliability policies) contract – a reference to the service contract that the service endpoint applies to
Service endpoints (Figure 4)
abstract the underlying service logic and implementation. You are
further able to choose which service operations from the service
contract to explicitly or implicitly expose.
The endpoint construct can be configured in the Web.Config file or the App.Config file, as shown here:
Example 12.
<system.serviceModel> <services> <service name="AccountService"> <endpoint name="EndPoint1" address="net.tcp://localhost:1234" binding="netTcpBinding" contract="IAccount" /> </service> </services> </system.serviceModel>
|
A single service can have more than one service endpoint. In the next example, the IAccount contract can be accessed on port 1234 using TCP, as well as port 8000 using HTTP:
Example 13.
<system.serviceModel> <services> <service name="AccountService"> <endpoint name="EndPoint1" address="net.tcp://localhost:1234" binding="netTcpBinding" contract="IAccount" /> <endpoint name="EndPoint3" address="http://localhost:8000" binding="basicHttpBinding" contract="IAccount" /> </service> </services> </system.serviceModel>
|
Note
The service endpoint, as expressed via the endpoint element, is further represented by the ServiceEndpoint class in WCF and is collectively comprised of an EndpointAddress class, a Binding class, and a ContractDescription class corresponding to the endpoint’s address, binding, and contract.
Address
Location
transparency is achieved using the endpoint address, which specifies
where the service can be found, as well as the transport protocol used
to communicate with it. The address attribute is assigned a Uniform Resource Identifier (URI), as follows:
Example 14.
[transport]://[domain or machine]:[optional port] net.tcp://localhost:1234
|
URIs are short strings
used to identify a resource over a network or the Web. For example,
“http://www.example.org:322/accounts.svc/secureEndpoint” is a valid URI.
This URI has four parts:
transport – specifies
the transport protocol used to reach the resource (for example, “http”,
“ftp”, “mailto”, “urn”, “mms”, etc.) location – the location of the server (for example, “www.example.org”) port – port where the resource is available (for example, “322”) path – the exact location of the resource (for example, “/accounts.svc/secureEndpoint”)
The sample URI we just introduced can be interpreted as follows: “The resource is located on the server www.example.org
and can be reached using the HTTP protocol. It can be accessed on port
322 and the path to it is “/accounts.svc/secureEndpoint.”
|
The endpoint address is represented in WCF by the EndpointAddress class, which includes a URI, an identity, and a collection of optional headers (Figure 2).
The primary purpose of this
class is to represent the URI. It also contains properties that include
an identity and a collection of optional headers. These headers are
used to provide additional, more
detailed information. For example, they may indicate which instance of
a service should be used to process an incoming message from a
particular consumer.
The endpoint address
can be specified imperatively in the code or declaratively through
configuration. Defining the endpoint address in configuration is
preferred, as the address and binding values used for development will
(hopefully) be different from the corresponding values used for when
the service is deployed in a production environment. Keeping the
address and binding in the configuration file allows them to change
without requiring recompilation and redeployment of the service.
|